package com.info1kclient;
import java.sql.Timestamp;
import java.util.Map;

import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;

public class MqttService extends Service implements MqttCallback {

	// Private instance variables
	private MqttClient client;
	private String brokerUrl;
	private MqttConnectOptions conOpt;  

	public MqttService() throws MqttException {
		super();
		
		// Set broker's URL here  
		this.brokerUrl = "tcp://test.mosquitto.orgs:1883";

		try {
			// Construct the object that contains options used when connecting the broker
			// such as CleanSession and password
			conOpt = new MqttConnectOptions();
			conOpt.setCleanSession(false);

			// Construct the MqttClient instance
			// NOTICE: The persistence mechanism is used to enable reliable messaging. 
			// For qualities of server (QoS) 1 or 2 to work, messages must be persisted 
			// to disk by both the client and the server. If this is not done, then a failure 
			// in the client or server will result in lost messages. 
			// If the application is only sending QoS 0 messages, then this is not needed.
			client = new MqttClient(this.brokerUrl,MqttClient.generateClientId(),null);

			// Set this MqttService as the callback handler when a message arrives.
			client.setCallback(this);

		} catch (MqttException e) {
			e.printStackTrace();
			log("Unable to set up client: "+e.toString());
			System.exit(1);
		}

	}

	/**
	 * Performs a single publish
	 * 
	 * @param topicName the topic to publish to
	 * @param qos the qos to publish at
	 * @param payload the payload of the message to publish
	 * @throws MqttException
	 */
	public void publish(String topicName, int qos, byte[] payload) throws MqttException {

		// Connect to the server
		client.connect();
		log("Connected to "+brokerUrl + " with client ID "+client.getClientId());

		// Construct an topic instance
		MqttTopic topic = client.getTopic(topicName);

		// Construct an instance of MQTT message
		MqttMessage message = new MqttMessage(payload);
		message.setQos(qos);

		// Publish the message
		String time = new Timestamp(System.currentTimeMillis()).toString();
		log("Publishing at: "+time+ " to topic \""+topicName+"\" qos "+qos);
		MqttDeliveryToken token = topic.publish(message);

		// Wait until the message has been delivered to the server
		token.waitForCompletion();

		// Disconnect the client
		client.disconnect();
		log("Disconnected");
	}

	/**
	 * Subscribe to multiple topics
	 * 
	 * @param topicName the topics set subcribed to 
	 * @param qos QoS set for corresponding topics
	 * @throws MqttException
	 */
	public void subscribe(String[] topicName, int[] qos) throws MqttException {

		// Connect to the broker
		client.connect();
		
		log("Connected to "+brokerUrl+" with client ID "+client.getClientId());
		
		for(int i=0; i<topicName.length;i++){
			log("Subscribing to topic \""+ topicName[i] +"\" qos "+qos[i]);
		}

		// perform subsribing to topics
		client.subscribe(topicName, qos);
	}

	/**
	 * Simply print info to standard out
	 *  
	 * @param message the message to log
	 */
	private void log(String message) {
			System.out.println(message);
	}

	/****************************************************************/
	/* Methods to implement the MqttCallback interface              */
	/****************************************************************/

	/**
	 * Called when the connection to the server has been lost
	 */
	@Override
	public void connectionLost(Throwable cause) {
		// An application may choose to implement reconnection
		// logic at this point.
		// This sample simply exits.
		log("Connection to " + brokerUrl + " lost!" + cause);
		System.exit(1);
	}

	/**
	 * Called when delivery for a message has been completed
	 * and all acknowledgments have been received.
	 */
	@Override
	public void deliveryComplete(MqttDeliveryToken token) {

	}

	/**
	 * This method is called when a message arrives from the server
	 */
	@Override
	public void messageArrived(MqttTopic topic, MqttMessage message) throws MqttException {

		String time = new Timestamp(System.currentTimeMillis()).toString();

		String clientMsg = "Time:\t" +time
				+ "  Topic:\t" + topic.getName()
				+ "  Message:\t" + new String(message.getPayload())
		+ "  QoS:\t" + message.getQos();
		log(clientMsg);
		
		// Show a notification when new message from broker is received
		showNotification(topic.getName(), new String(message.getPayload()));
		
	}

	/****************************************************************/
	/* End of MqttCallback methods                                  */
	/****************************************************************/

	/****************************************************************/
	/* Methods to implement Service                                 */
	/****************************************************************/

	private NotificationManager mNM;
	private int NOTIFICATION = 1; // Notification ID 
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {

		mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {

		// Get all the topics
		SharedPreferences topicStorage = getSharedPreferences(MainActivity.PREFS_NAME, 0);
		Map<String,Integer> topicAndQos = (Map<String, Integer>) topicStorage.getAll();
		
		// Get number of topics
		int numOfTopics = topicAndQos.size();
		
		if (numOfTopics>0){ 
			// multiple topics array
			String[] topics = new String[numOfTopics];
			// corresponding QoS level array
			int[] qos = new int[numOfTopics];
            
			// fill topics and qos to corresponding array
			int index = 0;
			for (Map.Entry<String, Integer> entry : topicAndQos.entrySet()) {
				topics[index] = entry.getKey();
				qos[index] = entry.getValue();
				index++;
			}
			
			// Perform subscribing
			try {
				subscribe(topics,qos);
			} catch (MqttException e) {
				e.printStackTrace();
			}
		}
		
		return START_STICKY;
	}

	@Override
	public void onDestroy() {

		// disconnect the client with broker
		try {
			client.disconnect();
		} catch (MqttException e) {
			e.printStackTrace();
		}
		
		// cancel the notification from status bar
		mNM.cancel(NOTIFICATION);
	}

	/****************************************************************/
	/* End of Service methods                                       */
	/****************************************************************/
	/**
	 * Show a notification with info such as received MQTT message and its topic 
	 * 
	 * @param topic the topic of received MQTT message
	 * @param mqttMsg the received MQTT message
	 */
	private void showNotification(String topic, String mqttMsg) {

		// Construct content tile of the notification
		CharSequence contentTitle = topic;

		// Construct content text of the notification
		CharSequence contentText = mqttMsg;

		// Construct scrolling text of the notification
		CharSequence scrollingText = "You have 1 new MQTT message";
		
		// Constructs a Notification object with icon, scrolling text and timestamp
		Notification notification = new Notification(R.drawable.ic_launcher, scrollingText,
				System.currentTimeMillis());

		// The PendingIntent to launch specific activity if the user selects this notification
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
				new Intent(this, MainActivity.class), 0);

		// Set the info for the views that show in the notification panel.
		notification.setLatestEventInfo(this, contentTitle,
				contentText, contentIntent);

		// Send the notification.
		mNM.notify(NOTIFICATION, notification);     
	}
}
